import java.awt.Point;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.FilenameFilter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;

/**
 * Ez az objektum tartalmazza a játék állapotváltozóit,
 * ez tölti be a pályákat, lép át a következő pályára, kezeli az életek számát.
 * Továbbá fogadja a mozgás illetve bombalerakás parancsokat.
 */
public class Game
{
  //Szintek listája
  private ArrayList levels = new ArrayList();
  //aktuális szint id-je
  private int levelIndex = 11;
  //játék futását jelzi
  private boolean boardStarted = false;
  //a játékhoz tartozó megjelenítő
  private IVisualizer Visualizer = null;
  //hátralévő életek száma
  private int lives = 3;
  //A mezőket tároló lista
  private ArrayList _fields = new ArrayList();
  //Időzítő
  private Timer _timer;
  //A pálya oszlopainak száma
  private int column = 1;

  private int lines = 1;
  //A jtáékososk listája
  //0 -> JuzKez
  //1 -> JuzLab
  private Player[] _players = new Player[2];

  //Az utoljára mozgatott játékos indexe
  private int lastStepPlayer = -1;
  //Pályán lévő játékosok száma
  private int playersLeft = 0;
  private int playerLeftDead = 0;

  //Be van e töltve a pálya
  private boolean mapLoaded = false;

  //Hátralevő gyémántok száma
  private int diamonds = 0;
  //Van-e a pályán felvehető uránium
  private boolean uraniumIsOnField;
  //A bombák robbanóereje
  private int bombPower = 1;
  private boolean playerMovesEnabled = true;
  private boolean paused = false;

  private static boolean printMap = false;

  //A bombák késleltetése
  private int bombTimer = 3;
  //EBbe kerül a futtatható állomány elérési útja
  private String mapsDir;
  //Maximális tolási mélység
  private int maxDepth = 2;

  /**
   * Itt inicializálódik a játék.
   */
  public void init()
  {
    BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
    String input;
    mapLoaded = false;

    //A futtatható állomány elérési útjának lekérése.
    File directory = new File (".");
    try
    {
      mapsDir = directory.getCanonicalPath();
    }
    catch (IOException ex)
    {
      System.exit(-2);
    }
    if(mapsDir.charAt(mapsDir.length()-1) != '\\')
      mapsDir += "\\";

    _timer = new Timer();

    File mapsDirFile = new File(mapsDir + "maps\\");

    FilenameFilter filter = new FilenameFilter() {
        public boolean accept(File dir, String name) {
            return name.endsWith(".txt");
        }};

    String[] files = mapsDirFile.list(filter);

    for(int i = 0;i< files.length;i++)
    {
      levels.add(files[i]);
    }


    if(!levels.isEmpty())
    {
      loadMap((String)levels.get(levelIndex));
      startBoard();
    }
    else
    {
      System.exit(-1);
    }
  }

  public void addVisualizer(IVisualizer iv)
  {
    Visualizer = iv;
  }

  public int getColumns()
  {
    return column;
  }

  public int getLines()
  {
    return lines - 1;
  }

  /**
   * Létrehoz egy új Multiplier szörnyet.
   * @return Az új szörny referenciája.
   */
  public Multiplier getNewMultiplier()
  {
    Multiplier multi = new Multiplier();
    _timer.add(multi);
    Visualizer.register(multi);
    return multi;
  }

  /**
   * Meghíváskor az utoljára lépett játékos elhagyja a pályát.
   */
  public void exitLastPlayer()
  {
    _players[lastStepPlayer].field.delete();
    if(playersLeft > 0)
    {
      playersLeft--;
      _players[lastStepPlayer].setAlive(false);
      //_players[lastStepPlayer] = null;
    }
    if(playersLeft == 0)
      nextLevel();
  }

  /**
   * Beállítja egy mező szomszédját egy másikra, a megadott irányba.
   * @param field1 A beállítandó mező referenciája.
   * @param direction Az irány.
   * @param field2 A szomszédnak beállytandó mező referenciája.
   */
  private void setNeighbour(int field1, int direction, int field2)
  {
    try
    {
      if (((direction == 3 || direction == 4) && ((field2 % column ) != 0)) ||
          ((direction == 1 || direction == 6) && ((field1 % column) != 0)) ||
          (direction == 2) ||
          (direction == 5))
        ((Field)_fields.get(field1)).setNeighbour(direction, ((Field)_fields.get(field2)));
    }
    catch (Exception e)
    {
      //ezt itt szépen benyeljük...
    }
  }

  /**
   * Feltölt egy szintet a megadott páylaleíró alapján.
   * @param filename A betöltendő pályát tartalmazó file elérési útja.
   */
  private boolean loadMap(String filename)
  {
    BufferedReader in = null;

    //AZ oszlopok számát nyilvántartó változó
    column = 0;

    //Az aktuális sort nyilvántartó változó
    int cLine = 1;

    Field field;
    Entity entity;

    //Alapvető pályaváltozók nullázása.
    _fields.clear();
    _timer.clear();
    Visualizer.setLoading(true);
    Visualizer.reset();
    playerLeftDead = 0;
    playersLeft = 0;
    boardStarted = false;
    _players[0] = null;
    _players[1] = null;
  //  playerKilled = false;
    diamonds = 0;

    try
    {
      File file = new File(mapsDir + "maps\\" + filename);
      if(!file.exists())
      {
        System.out.println("File does not exist.");
        return false;
      }
      in = new BufferedReader(new FileReader(file));
      while(in.ready())
      {
        //A beolvasott sor felsolgozása
        String currentLine = in.readLine();
        if(currentLine.equals(""))
          return false;
        
        if(column == 0)
          column = currentLine.length();

        //A sorban szereplő mezőket létrehozó ciklus
        for(int i = 0; i< currentLine.length() ;i++)
        {
          field = new Field(this);
          switch(currentLine.charAt(i))
          {
            case 'w':
              entity = new Wall();
              break;
            case 'e':
              entity = new Exit();
              break;
            case 'p':
              entity = new Player();
              _players[1] = (Player)entity;
              playersLeft++;
               playerLeftDead++;
              break;
            case 'P':
              entity = new Player();
              _players[0] = (Player)entity;
              playersLeft++;
              playerLeftDead++;
              break;
            case 's':
              entity = new Sandstone();
              break;
            case 'd':
              entity = new Diamond();
              diamonds++;
              break;
            case 'g':
              entity = new Granite();
              break;
            case 'u':
              entity = new Uranium();
              setUranium();
              break;
            case 'M':
              entity = new Multiplier();
              _timer.add((ITickable)entity);
              break;
            case 'b':
              entity = new Boulder();
              break;
            case 'T':
              entity = new Tracker();
              _timer.add((ITickable)entity);
              break;
            case 'B':
              entity = new Bomb(bombPower, bombTimer);
              _timer.add((ITickable)entity);
              entity.alive = true;
              break;
            case 'G':
              entity = new Gemster();
              _timer.add((ITickable)entity);
              break;
            default:
              entity = null;
              break;
          }
          if(entity != null)
          {
            entity.setField(field);
            field.add(entity);
            Visualizer.register(entity);
          }
          _fields.add(field);
        }

        cLine++;
      }
    }
    catch (Exception e)
    {
      return false;
    }
    finally
    {
      try
      {
        in.close();
      }
      catch(Exception e)
      {
        return false;
      }
    }

    //linkek létrehozása
    for (int i = 0; i < _fields.size(); i++)  //i az aktuális mező indexe
    {
      //lefelé
      setNeighbour(i, 5, i + column);
      //felfelé
      setNeighbour(i, 2, i - column);
      //balra fel
      setNeighbour(i, 1, i - 1 - (((i%column)+1)%2)*column);
      //jobbra le
      setNeighbour(i, 4, i + 1 + ((i%column)%2)*column);
      //balra le
      setNeighbour(i, 6, i - 1 + ((i%column)%2)*column);
      //jobbra fel
      setNeighbour(i, 3, i + 1 - (((i%column)+1)%2)*column);
    }

    mapLoaded = true;
    lines = cLine;
    return true;
  }

  /**
   * Visszaadja egy adott field elhelyezkedését a pályán. (elsősorban
   * megjelenítéshez)
   * @param field A keresett mező
   * @return A kért mező pozíciója.
   */
  public Point getPos(Field field)
  {
    //if(field.getEntity() instanceof Bomb)
   // {
  //    System.out.print("");
  //  }
    int i;
    for(i = 0;i<_fields.size();i++)
    {
      if(((Field)_fields.get(i)) == field)
        break;
    }

    int x = i % column;
    int y = i / column;

    return new Point(x, y);
  }

  /**
   * Lépteti a játékost egy adott irányba. Illetve megjegyzi az utoljára mozgó
   * játékost.
   * @param direction A lépés iránya.
   * @param player A léptetendő játékos.
   */
  public void step(int direction, int player)
  {
    if(boardStarted && _players[player] != null && playerMovesEnabled)
    {
      _players[player].move(direction*-1, maxDepth);
      lastStepPlayer = player;
    }
  }

  /**
   * Beállítható, hogy a játékos tudjon e lépni. (szikla gördülés közben pl. ne)
   * @param value
   */
  public void setPlayerMoves(boolean value)
  {
    playerMovesEnabled = value;
  }

  /**
   * Alaphelyzetbe állítja a pályát.
   */
  public void reset()
  {
    loadMap((String)levels.get(levelIndex));
    startBoard();
  }

  /**
   * Szünetelteti a játékot.
   */
  public void pause()
  {
    _timer.stop();
    paused = true;
    setPlayerMoves(false);
  }

  /**
   * Csak a pálya elindítása lesz a feladata.
   * A timert engedélyezi, illetve reseteli a megjelenítőt.
   */
  public void startBoard()
  {
    _timer.start();
    boardStarted = true;
    Visualizer.setLoading(false);
  }

  /**
   * A következő pályára lép.
   * @return Igaz, ha volt pálya még.
   */
  public boolean nextLevel()
  {
    if(diamonds == 0)
    {
      levelIndex = levelIndex + 1 < levels.size() ? levelIndex + 1 : 0;
      loadMap((String)levels.get(levelIndex));
      startBoard();
      return true;
    }

    return false;

  }


  /**
   * Visszaadja a program elérési útját.
   * @return Az elérési út.
   */
  public String getMainDir()
  {
    return mapsDir;
  }

  /**
   * Eggyel növeli a felszedett gyémántok számát.
   */
  public void addDiamond()
  {
    if(diamonds > 0)
      diamonds--;
  }

  /**
   * Megmondja, hogyminden gyémánt fel lett-e szedve, elhagyható-e a pálya.
   * @return True, ha elhagyható, False, ha nem
   */
  public boolean canLeaveMap()
  {
    return diamonds == 0;
  }

  /**
   * Valamelyik játékos meghal
   */
  public void killPlayer()
  {
    if(playerLeftDead > 1)
    {
      playerLeftDead--;
    }
    else
    {
      if(lives > 0)
      {
        _timer.stop();
        lives--;
        Visualizer.playerKilled(lives);

        reset();
      }
      else
      {
        Visualizer.playerKilled(-1);
        System.exit(0);
      }
    }
 //   if(!playerKilled)
 //   {
 //     playerKilled = true;
     // _timer.stop();
    //  lives--;
  //    Visualizer.playerKilled(lives);

 //     if(lives >= 0)
 //     {
//        reset();
//      }
//      else
//      {
//        System.exit(0);
//      }
//      TODO
 //   }
 
  }

  /**
   * Letetet egy bombát a kiválasztott játékossal.
   * @param player A játékos amellyel bombát akarunk lerakni.
   */
  public void setBomb(int player)
  {
    _players[player].setBomb();
  }

  /**
   * Létrehozza a Bomb osztály egy példányát, majd visszaadja a hívónak.
   * @return
   */
  public Bomb createBomb()
  {
    Bomb b = new Bomb(bombPower, bombTimer);
    Visualizer.register(b);
    return b;
  }

  /**
   * Létrehozza a Diamond osztály egy példányát, majd visszaadja a hívónak.
   * @return
   */
  public Diamond createDiamond()
  {
    Diamond d = new Diamond();
    Visualizer.register(d);
    return d;
  }

  /**
   * Megnöveli a bombák hatósugarát.
   */
  public void increaseBombStrength()
  {
    bombPower++;
  }

  /**
   * Lerak egy darab uránt a pályára, egy szaporodó szörny halála esetén.
   */
  public void setUranium()
  {
    uraniumIsOnField = true;
  }

  /**
   * Létrehoz egy új Uranium-ot.
   * @return Az új Uranium referenciája.
   */
  public Uranium getNewUranium()
  {
    Uranium u = new Uranium();
    Visualizer.register(u);
    return u;
  }

  /**
   * Visszaadja, hogy van-e a pályán urán.
   * @return
   */
  public boolean getUranium()
  {
    return uraniumIsOnField;
  }

  /**
   * Visszaadja az egyik playert reprezentáló referenciát.
   * @return A kért player.
   */
  public Player getPlayer(int param)
  {
    return _players[param];
  }

  /**
   * Visszaadja az időzítőt.
   * @return A Timerre mutató referencia.
   */
  public Timer getTimer()
  {
    return _timer;
  }
}